#include "PhysicsManager.h"
#include "..\Debug\DebugLog.h"
#include "..\rapidxml.hpp"
#include "..\ScaleFactor.h"
#include "..\Collision\CollisionListener.h"
#include "..\EnumDefine.h"

#ifdef _WIN32
#include "RenderManager.h"
#include "..\Engines\SFML2DRenderEngine.h"
#endif

#include <fstream>

PhysicsManager::PhysicsManager(void) : m_debugDraw(nullptr), m_pause(false)
{
	m_world = new b2World(b2Vec2(0.0f, 9.8f));
	m_contactListener = new CollisionListener;
	m_world->SetContactListener(m_contactListener);
}

PhysicsManager::~PhysicsManager(void)
{
	if (m_debugDraw != nullptr)
	{
		delete m_debugDraw;
		m_debugDraw = nullptr;
	}
	delete m_contactListener;
	m_contactListener = nullptr;
	delete m_world;
	m_world = nullptr;
	m_bodiesToDestroy.clear();
	m_jointsToDestroy.clear();
	m_syncList.clear();
}

PhysicsManager* PhysicsManager::m_instance = nullptr;

PhysicsManager& PhysicsManager::getSingleton()
{
	if (m_instance == nullptr)
	{
		m_instance = new PhysicsManager;
	}
	return *m_instance;
}

void PhysicsManager::clean()
{
	if (m_instance != nullptr)
	{
		delete m_instance;
		m_instance = nullptr;
	}
}

b2World& PhysicsManager::getWorld()
{
	return *m_world;
}

void PhysicsManager::step()
{
	if (!m_pause)
	{
		if (!m_events.empty())
		{
			for (auto it = m_events.begin(); it != m_events.end(); it++)
				(*it)();
			m_events.clear();
		}

		float timeStep = 1.0f/60.0f;
		m_world->Step(timeStep, 8, 3);
	}
}

void PhysicsManager::setDebugDraw(b2Draw* p_debugDraw)
{
	if (m_debugDraw != nullptr)
	{
		delete p_debugDraw;
	}
	else
	{
		m_debugDraw = p_debugDraw;
		m_world->SetDebugDraw(p_debugDraw);
		p_debugDraw->AppendFlags(b2Draw::e_jointBit);
		p_debugDraw->AppendFlags(b2Draw::e_shapeBit);
	}
}

void PhysicsManager::drawDebugData()
{
#ifdef _WIN32
	SFML2DRenderEngine* re = static_cast<SFML2DRenderEngine*>(RenderManager::getSingleton().getRenderEngine());
	re->setView(1);
#endif
	m_world->DrawDebugData();
}

void PhysicsManager::flush()
{
	if (!m_jointsToDestroy.empty())
	{
		for (auto it = m_jointsToDestroy.begin(); it != m_jointsToDestroy.end(); it++)
		{
			m_world->DestroyJoint((*it));
			(*it) = nullptr;
		}
		m_jointsToDestroy.clear();
	}
	if (!m_bodiesToDestroy.empty())
	{
		for (auto it = m_bodiesToDestroy.begin(); it != m_bodiesToDestroy.end(); it++)
		{
			m_world->DestroyBody((*it));
			(*it) = nullptr;
		}
		m_bodiesToDestroy.clear();
	}
}

void PhysicsManager::reset()
{
	if (m_debugDraw != nullptr)
	{
		delete m_debugDraw;
		m_debugDraw = nullptr;
	}

	delete m_contactListener;
	m_contactListener = nullptr;
	delete m_world;
	m_world = new b2World(b2Vec2(0.0f, 9.8f));
	m_bodiesToDestroy.clear();
	m_jointsToDestroy.clear();
	m_syncList.clear();
}

void PhysicsManager::createCollision(const std::string& p_filepath)
{
	std::string xmlContent;
	std::ifstream file(p_filepath);
	if (file)
	{
		std::string line;
		while(std::getline(file, line))
			xmlContent += line;
	}
	else
	{
		Debug::log("ERROR: Cannot find " + p_filepath + " when creating collision");
		return;
	}

	std::vector<char> xmlBuffer = std::vector<char>(xmlContent.begin(), xmlContent.end());
	xmlBuffer.push_back('\0');
	rapidxml::xml_document<> doc;
	doc.parse<rapidxml::parse_no_data_nodes>(&xmlBuffer[0]);
	rapidxml::xml_node<>* root = doc.first_node();
	rapidxml::xml_node<>* block = root->first_node("block");
	float tileSize = static_cast<float>(atof(root->first_attribute("tilesize")->value()));
	while (block)
	{
		std::vector<b2Vec2> points;
		rapidxml::xml_node<>* point = block->first_node("point");
		while (point)
		{
			float x = static_cast<float>(atof(point->first_attribute("x")->value()));
			float y = static_cast<float>(atof(point->first_attribute("y")->value()));
			points.push_back(b2Vec2((x * tileSize) * SCALEF, (y * tileSize) * SCALEF));
			point = point->next_sibling();
		}

		if (!points.empty())
		{
			float x = static_cast<float>(atof(block->first_attribute("x")->value()));
			float y = static_cast<float>(atof(block->first_attribute("y")->value()));
			b2ChainShape chainShape;
			chainShape.CreateChain(&points.front(), points.size());
			b2BodyDef bodyDef;
			bodyDef.type = b2_staticBody;
			bodyDef.position.Set((x * tileSize) * SCALEF, (y * tileSize) * SCALEF);
			b2FixtureDef fixtDef;
			fixtDef.shape = &chainShape;
			fixtDef.filter.categoryBits = COLLF_WORLD;
			fixtDef.filter.maskBits = COLLF_PLAYER | COLLF_PLAYER_BULLET | COLLF_PLAYER_SENSOR | COLLF_ENEMY;
			fixtDef.userData = (void*)FIXT_ID_FLOOR;
			b2Body* body = m_world->CreateBody(&bodyDef);
			body->CreateFixture(&fixtDef);
			points.clear();
		}
		block = block->next_sibling();
	}
}

void PhysicsManager::removeSpriteSync(vn::Sprite* p_sprite)
{
	for (auto it = m_syncList.begin(); it != m_syncList.end(); it++)
	{
		if ((*it).first == p_sprite)
		{
			m_syncList.erase(it);
			break;
		}
	}
}

void PhysicsManager::removeSpriteSync(b2Body* p_b2Body)
{
	for (auto it = m_syncList.begin(); it != m_syncList.end(); it++)
	{
		if ((*it).second == p_b2Body)
		{
			m_syncList.erase(it);
			break;
		}
	}
}

void PhysicsManager::addSpriteSync(b2Body* p_b2Body, vn::Sprite* p_sprite)
{
	m_syncList.push_back(std::make_pair(p_sprite, p_b2Body));
}

void PhysicsManager::syncSprites()
{
	if (!m_syncList.empty())
	{
		for (auto it = m_syncList.begin(); it != m_syncList.end(); it++)
		{
			(*it).first->setPosition((*it).second->GetWorldCenter().x / SCALEF, (*it).second->GetWorldCenter().y / SCALEF);
		}
	}
}